package com.iocup.keybastion.spring.starter.config;

import com.iocup.keybastion.common.Cookie;
import com.iocup.keybastion.context.WebContextHolder;
import com.pine.sunflower.core.utils.RequestUtils;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * @author xyjxust
 * @create 2022/3/2 10:10
 **/
public class JeeWebContextHolder implements WebContextHolder {

    private HttpServletRequest request;
    private HttpServletResponse response;

    private static ZoneId GMT = ZoneId.of("GMT");
    /**
     * Date formats with time zone as specified in the HTTP RFC to use for formatting.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.1">Section 7.1.1.1 of RFC 7231</a>
     */
    private static DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US).withZone(GMT);

    private String bodyMap;

    public JeeWebContextHolder(HttpServletRequest request, HttpServletResponse response) {
        this.request = request;
        this.response = response;
    }

    @Override
    public Optional<String> getRequestParameter(String name) {
        return Optional.ofNullable(request.getParameter(name));
    }

    @Override
    public Map<String, String> getRequestParameters() {
        return RequestUtils.getParameters(request);
    }

    @Override
    public Optional getRequestAttribute(String name) {
        return Optional.ofNullable(request.getAttribute(name));
    }

    @Override
    public WebContextHolder setRequestAttribute(String name, Object value) {
        request.setAttribute(name, value);
        return this;
    }

    @Override
    public String getRequestMethod() {
        return request.getMethod();
    }

    @Override
    public String getRemoteAddress() {
        return RequestUtils.getRequestIp(request);
    }

    @Override
    public Optional<String> getRequestHeader(String name) {
        return Optional.ofNullable(request.getHeader(name));
    }

    @Override
    public WebContextHolder setResponseHeader(String name, String value) {
        response.setHeader(name, value);
        return this;
    }

    @Override
    public Optional<String> getResponseHeader(String name) {
        return Optional.ofNullable(response.getHeader(name));
    }

    @Override
    public WebContextHolder setResponseContentType(String content) {
        response.setContentType(content);
        return this;
    }

    @Override
    public Collection<Cookie> getRequestCookies() {
        final Collection<Cookie> pac4jCookies = new LinkedHashSet<>();
        final javax.servlet.http.Cookie[] cookies = this.request.getCookies();

        if (cookies != null) {
            for (final javax.servlet.http.Cookie c : cookies) {
                final Cookie cookie = new Cookie(c.getName(), c.getValue());
                cookie.setDomain(c.getDomain());
                cookie.setHttpOnly(c.isHttpOnly());
                cookie.setMaxAge(c.getMaxAge());
                cookie.setPath(c.getPath());
                cookie.setSecure(c.getSecure());
                pac4jCookies.add(cookie);
            }
        }
        return pac4jCookies;
    }

    @Override
    public String getRequestURL() {
        return request.getRequestURI();
    }

    @Override
    public String getFullRequestURL() {
        final StringBuffer requestURL = request.getRequestURL();
        final String queryString = request.getQueryString();
        if (queryString == null) {
            return requestURL.toString();
        }
        return requestURL.append('?').append(queryString).toString();
    }

    @Override
    public WebContextHolder addResponseCookie(Cookie cookie) {
        this.response.addHeader("Set-Cookie", createCookieHeader(cookie));
        return this;
    }

    @Override
    public WebContextHolder setResponseStatus(int status) {
        this.response.setStatus(status);
        return this;
    }

    @Override
    public WebContextHolder addCookie(String name, String value, String path, String domain, int timeout) {
        Cookie cookie = new Cookie(name, value);
        cookie.setDomain(domain);
        cookie.setPath(path);
        cookie.setMaxAge(timeout);
        this.response.addHeader("Set-Cookie", createCookieHeader(new Cookie(name, value)));
        return this;
    }

    @Override
    public WebContextHolder setResponseServer(String value) {

        return this;
    }

    @Override
    public void responseRedirect(String url) {
        try {
            this.response.sendRedirect(url);
        } catch (IOException e) {
            throw new SecurityException("转发错误", e);
        }
    }

    @Override
    public void forward(String path) {
        try {
            this.request.getRequestDispatcher(path).forward(request,response);
        } catch (ServletException |IOException e) {
            throw new SecurityException("转发错误", e);
        }
    }

    @Override
    public void responseOutput(Object o) {
        try {
            this.response.getWriter().println(o);
        } catch (IOException e) {
            throw new SecurityException("数据写入错误", e);
        }
    }

    @Override
    public Optional<String> getRequestContent() {
        //如果不是json就不进行解析
        String contentType = request.getHeader("content-type");
        if (StringUtils.isBlank(contentType) || (!contentType.startsWith("application/json")&&!contentType.startsWith("application/x-www-form-urlencoded"))) {
            return Optional.empty();
        }
        if(bodyMap!=null){
            return Optional.ofNullable(bodyMap);
        }
        InputStream is = null;
        StringBuilder sb = new StringBuilder();
        try {
            is = request.getInputStream();
            byte[] b = new byte[4096];
            for (int n; (n = is.read(b)) != -1; ) {
                sb.append(new String(b, 0, n));
            }
        } catch (IOException e) {
            throw new SecurityException("解析请求body内容错误", e);
        } finally {
            if (null != is) {
                try {
                    is.close();
                } catch (IOException e) {

                }
            }
        }
        bodyMap= sb.toString();
        return Optional.ofNullable(bodyMap);
    }


    public static String createCookieHeader(Cookie cookie) {
        StringBuilder builder = new StringBuilder();
        builder.append(String.format("%s=%s;", cookie.getName(), cookie.getValue()));

        if (cookie.getMaxAge() > -1) {
            builder.append(String.format(" Max-Age=%s;", cookie.getMaxAge()));
            long millis = cookie.getMaxAge() > 0 ? System.currentTimeMillis() + (cookie.getMaxAge() * 1000) : 0;
            Instant instant = Instant.ofEpochMilli(millis);
            ZonedDateTime time = ZonedDateTime.ofInstant(instant, GMT);
            builder.append(String.format(" Expires=%s;", DATE_FORMATTER.format(time)));
        }
        if (StringUtils.isNotBlank(cookie.getDomain())) {
            builder.append(String.format(" Domain=%s;", cookie.getDomain()));
        }
        builder.append(String.format(" Path=%s;", StringUtils.isNotBlank(cookie.getPath()) ? cookie.getPath() : "/"));

        String sameSitePolicy = cookie.getSameSitePolicy() == null ? "lax" : cookie.getSameSitePolicy().toLowerCase();
        switch (sameSitePolicy) {
            case "strict":
                builder.append(" SameSite=Strict;");
                break;
            case "none":
                builder.append(" SameSite=None;");
                break;
            case "lax":
            default:
                builder.append(" SameSite=Lax;");
                break;
        }
        if (cookie.isSecure() || "none".equals(sameSitePolicy)) {
            builder.append(" Secure;");
        }
        if (cookie.isHttpOnly()) {
            builder.append(" HttpOnly;");
        }
        String value = builder.toString();
        if (value.endsWith(";")) {
            value = value.substring(0, value.length() - 1);
        }
        return value;
    }
}
